18장. Event Sourcing의 불변성과 보상 전략
이벤트는 왜 수정하면 안 되는가
Event Sourcing에서는
이벤트가 시스템의 기준 데이터(Source of Truth)다.
예를 들어 이런 기록이 있다고 하자.
PointEarned(10000)
PointSpent(3000)
이 기록은 “일어난 사실”이다.
- 10,000 포인트가 적립되었고
- 3,000 포인트가 사용되었다
이것은 과거의 사실이다.
Event Sourcing에서는
이미 발생한 사실을 나중에 수정하지 않는다.
이 원칙을 불변성(Immutable) 이라고 한다.
그런데 잘못 차감되었다면?
문제 상황을 보자.
PointEarned(10000)
PointSpent(3000)
그런데 알고 보니
3,000 포인트가 잘못 차감되었다.
상태 기반 시스템이라면 이렇게 할 수 있다.
balance = balance + 3000
하지만 Event Sourcing에서는
기존 이벤트를 수정하지 않는다.
대신 새로운 이벤트를 추가한다.
PointEarned(10000)
PointSpent(3000)
PointRefunded(3000)
과거를 지우지 않고
새로운 사실을 기록한다.
이것이 보상 이벤트(Compensating Event) 다.
왜 이렇게까지 해야 하는가
이 방식에는 중요한 의미가 있다.
- 모든 변경 이력이 남는다.
- 무엇이 잘못되었는지 추적 가능하다.
- 감사(Audit)와 분쟁 대응이 가능하다.
이벤트는 로그가 아니라
시스템의 공식 기록이기 때문이다.
이벤트 버전 관리란 무엇인가
여기서 또 하나의 질문이 생긴다.
이벤트 구조가 바뀌면 어떻게 하나?
예를 들어 처음에는 이벤트가 이랬다고 하자.
{
"type": "PointEarned",
"amount": 10000
}
나중에 정책이 바뀌어
적립 사유(reason)를 추가해야 한다.
{
"type": "PointEarned",
"amount": 10000,
"reason": "promotion"
}
여기서 중요한 점은:
테이블을 두 개 만들지 않는다.
DDL을 매번 변경하지 않는다.
보통 Event Store는 이렇게 생긴다.
event_store
---------------------------------
id
aggregate_id
event_type
event_version
payload (JSON)
created_at
payload는 JSON이다.
구조가 바뀌면:
- 기존 이벤트는 version = 1
- 새 이벤트는 version = 2
로 저장한다.
테이블은 그대로 유지한다.
이벤트 삭제는 가능한가
원칙적으로는 삭제하지 않는다.
왜냐하면:
- 삭제는 과거를 없애는 행위이기 때문이다.
- Replay 시 상태가 달라질 수 있다.
다만 현실에서는 다음과 같은 경우가 있다.
- 법적 삭제 요구(GDPR 등)
- 민감 정보 제거 필요
이 경우에는:
- 암호화
- 마스킹
- 이벤트 재작성(Migration)
같은 전략이 필요하다.
Event Sourcing은 단순한 기술 패턴이 아니라
운영 전략까지 포함한다.
순수 구조와 현실적인 구조
순수 Event Sourcing
- balance 컬럼 없음
- 항상 이벤트 기반 계산
- Snapshot 사용
현실적 혼합 구조
- 이벤트는 저장
- balance 테이블도 유지
- 이벤트 INSERT + balance UPDATE를 한 트랜잭션으로 처리
실무에서는 혼합 구조를 많이 사용한다.
정리
Event Sourcing에서 중요한 원칙은 세 가지다.
- 이벤트는 불변이다.
- 잘못된 처리는 보상 이벤트로 해결한다.
- 이벤트 버전 관리는 DDL 변경이 아니라 메시지 진화다.